/*** Created Time: 2022年5月19日 15:08:04 ***/
/*** Author: Eric                        ***/

#include <onix/io.h>
#include <onix/interrupt.h>
#include <onix/assert.h>
#include <onix/debug.h>
#include <onix/task.h>

#define     PIT_CHAN0_REG   (0X40)      // 8253计数器0，用于产生时钟信号
#define     PIT_CHAN2_REG   (0X42)      // 8253计数器2，用于内部扬声器发出不同音调的声音，
                                        // 原理是给扬声器输送某频率的方波
#define     PIT_CTRL_REG    (0X43)      // 8253控制字寄存器

#define     HZ              (100)
#define     OSCILLATOR      (1193182)   // 振荡器频率
#define     CLOCK_COUNTER   (OSCILLATOR / HZ)   // 记录发生的时钟中断次数
#define     JIFFY           (1000 / HZ)         // 每发生一次中断，JIFFY变数加一

#define     SPEAKER_REG     (0x61)      // PC Speaker，即蜂鸣器寄存器地址
#define     BEEP_HZ         (440)       // 蜂鸣器频率
#define     BEEP_COUNTER    (OSCILLATOR / BEEP_HZ)


// 辅助函数声明
void start_beep(void);
void stop_beep(void);
void clock_handler(int vector);
void pit_init(void);


// 时间片计数器
u32 volatile jiffies = 0;
u32 jiffy = JIFFY;

// 当大于0时表示正在播放声音，为0时表示未播放声音
u32 volatile beeping = 0;

void start_beep(void)
{
    if (!beeping) {
        outb(SPEAKER_REG, inb(SPEAKER_REG) | 0b11);
    }
    beeping = jiffies + 5;
}

void stop_beep(void)
{
    if (beeping && jiffies > beeping) {
        outb(SPEAKER_REG, inb(SPEAKER_REG) & 0xfc);
        beeping = 0;
    }
}

// 时钟中断处理函数
void clock_handler(int vector)
{
    assert(vector == 0x20);
    send_eoi(vector); // 发送中断处理结束符

    stop_beep();      // 检测并停止蜂鸣器
    task_wakeup();    // 唤醒睡眠结束的任务

    jiffies++;        // 每发生一次中断，jiffies变数加一
    // DEBUGK("clock jiffies %d ...\n", jiffies);

    task_t *task = running_task();      // 得到当前任务
    assert(task->magic == ONIX_MAGIC);  // 确保任务栈没有发生溢出

    task->jiffies = jiffies;
    task->ticks--;
    if (!task->ticks)
    {
        schedule();
    }
}

extern u32 startup_time;

time_t sys_time(void)
{
    return startup_time + (jiffies * JIFFY) / 1000;
}

void pit_init(void)
{
    // 配置8253计数器 0 时钟
    outb(PIT_CTRL_REG, 0b00110100);
    outb(PIT_CHAN0_REG, CLOCK_COUNTER & 0xff);          // 先读取计数器0低字节
    outb(PIT_CHAN0_REG, (CLOCK_COUNTER >> 8) & 0xff);   // 再读取计数器0高字节

    // 配置计数器 2 蜂鸣器
    outb(PIT_CTRL_REG, 0b10110110);
    outb(PIT_CHAN2_REG, (u8)BEEP_COUNTER);
    outb(PIT_CHAN2_REG, (u8)(BEEP_COUNTER >> 8));
}

void clock_init(void)
{
    pit_init();     // 初始化计数器
    set_interrupt_handler(IRQ_CLOCK, clock_handler);    // 将时钟处理函数指针赋到中断处理函数表中
    set_interrupt_mask(IRQ_CLOCK, true);    // 打开时钟中断
}
